package com.server;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import java.util.HashMap;

public class Server {
	
	private boolean running = true;
	
	private DatagramSocket socket = null;
	private DatagramPacket packet = null;
	
	private HashMap<String, Location> positions = null;
	private HashMap<String, Long> timeAdded = null;
	
	private int port = 2603;
	private byte[] buffer = null;
	
	private Long idleTimeAceppted = new Long(10);
	
	private String resetPassword = "1946384968372";
	private String killPassword = "386648647836";
	
	private String tag_receivedPosition = "mpos";
	private String tag_requestPosition = "rpos";
	private String tag_reset = "reset";
	private String tag_kill = "kill";
	private String tag_ping = "ping";
	
	public static void main(String[] args) {
		new Server();
    }
	
	/**
	 * Starts a server with default port 2603
	 */
	public Server(){
		
		this(2603);
		
	}
	
	/**
	 * Starts a server with the specified port
	 * 
	 * @param port - port to listen on
	 */
	public Server(int port){
		
		this.port = port;
		
		/**
		 * Creates a datagrampacket on the selected port
		 */
		try {
			socket = new DatagramSocket(port);
		} catch (SocketException e) {
			e.printStackTrace();
		}
		
		/**
		 * Creates a hashmap with locations, with the users names as keys
		 * 
		 * Sets the buffer size of messages to length of tags and punctuation marks as: 
		 * [taglength+:+maxNameLength+:+latitudeLength+:+longitudelength+:+;]
		 */
		positions = new HashMap<String, Location>();
		timeAdded = new HashMap<String, Long>();
		buffer = new byte[4+1+12+1+9+1+9+1+1];
		
		System.out.println("[Server] - Buffer created with length "+buffer.length);
		
		/**
		 * Initiates the packet with the specified buffersize
		 */
		packet = new DatagramPacket(buffer, buffer.length);
		
		String unparsed = null;
		String[] parsed = null;
		String remoteAdress = null;
		int remotePort = 0;
		
		// Thread loop
		while(running){

			System.out.println("[Server] ------------Listening for packets--------------");
			
			/**
			 * Reads package, sleeps while none is recieved
			 */
			try {
				socket.receive(packet);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			unparsed = new String(packet.getData()).toLowerCase();
			parsed = splitAndClean(unparsed);
			
			remoteAdress = packet.getAddress().getHostAddress();
			remotePort = packet.getPort();
			
			System.out.println("[Server] - Recieved: "+unparsed+" From: "+remoteAdress+":"+remotePort);
			
			if(parsed[0].equals(tag_receivedPosition)){
				updatePosition(parsed);
			}else if(parsed[0].equals(tag_requestPosition)){
				sendAllPositions(parsed);
			}else if(parsed[0].equals(tag_reset)){
				reset(parsed);
			}else if(parsed[0].equals(tag_kill)){
				kill();
			}else if(parsed[0].equals(tag_ping)){
				respondPong();
			}
			
		}
		
		socket.close();
		
	}
	
	private void respondPong() {
		
		String messageToSend = "pong:;";
		
		System.out.println("[Server] - Sending "+messageToSend);
		
		setPackageMessage(messageToSend);
		
		try {
			socket.send(packet);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	/**
	 * Splits the message from the client, first on ";", takes the first element, then splits again on ":"
	 * 
	 * @param message - message to be splitted  
	 * @return String[] - result parameters
	 */
	private String[] splitAndClean(String message){
		String[] splitted = message.split(";");
		splitted = splitted[0].split(":");
		return splitted;
	}
	
	/**
	 * resets the list of positions on the server
	 * 
	 * @param parsed - reset command/code
	 */
	private void reset(String[] parsed) {
		if(parsed[1].equals(resetPassword)){
			positions.clear();
		}
	}
	
	/**
	 * Sets the message in packet to message, and fills the rest with "X" to prevent data reuse bugs
	 * 
	 * @param message - message to be set in package
	 */
	private void setPackageMessage(String message){
		
		String fillString = "";
		
		for(int i = 0; i<buffer.length; i++){
			if(i < message.length()){
				fillString += message.charAt(i);
			}else{
				fillString += "X";
			}
		}
		
		byte[] toFill = fillString.getBytes();
		
		packet.setData(toFill);
			
	}
	
	/**
	 * Adds the position to the list if it didn't already exist, updates it if it does
	 * 
	 * @param parsed - String[] containing name, latitude & longitude 
	 */
	private void updatePosition(String[] parsed) {
		updateTimeAdded(parsed[1]);
		if(positions.containsKey(parsed[1])){
			positions.remove(parsed[1]);
		}else{
			System.out.println("[Server] - adding "+parsed[1]);
		}
		int latitude = Integer.parseInt(parsed[2]);
		int longitude = Integer.parseInt(parsed[3]);
		Location loc = new Location(longitude, latitude, 0);
		positions.put(parsed[1], loc);
	}
	
	/**
	 * returns in seconds, time since last updated [name]
	 * @param name
	 * @return
	 */
	private Long getTimeSinceLastUpdate(String name){
		Long last = timeAdded.get(name);
		if(last == null){
			last = new Long(0);
		}
		return ((System.currentTimeMillis()-last)/1000);		
	}
	
	private void updateTimeAdded(String name) {
		if(timeAdded.containsKey(name)){
			timeAdded.remove(name);
		}
		timeAdded.put(name, System.currentTimeMillis());
	}

	/**
	 * Sends all positions to the last recieved packets owner
	 * 
	 * @param parsed - parsed message from packets owner, used to exclude his/her position 
	 */
	private void sendAllPositions(String[] parsed) {
		if(parsed.length > 1){
			Set<String> keys = positions.keySet();
			List<String> toRemove = new ArrayList<String>();
			Iterator iterator = keys.iterator();
			while(iterator.hasNext()){
				Object nextKey = iterator.next();
				String keykey = (String)nextKey;
				Long timeSinceLast = getTimeSinceLastUpdate(keykey);
				
				if(timeSinceLast>idleTimeAceppted){
					toRemove.add(keykey);
				}else{
					if(parsed.length==1){
						sendPosition(nextKey);
					}
					if(!parsed[1].equals(keykey)){
						sendPosition(nextKey);
					}
				}
			}
			for(int i = 0; i<toRemove.size(); i++){
				positions.remove(toRemove.get(i));
				timeAdded.remove(toRemove.get(i));
			}
		}
	}
	
	/**
	 * Sends the position of [key] to the last received packets owner
	 * 
	 * @param key - key used to locate the right location in the hashmap
	 */
	private void sendPosition(Object key){

		Location location = positions.get(key);
		
		String messageToSend = "";
		
		messageToSend += "mPos"+":";
		messageToSend += (String)key+":";
		messageToSend += location.getLatitude()+":";
		messageToSend += location.getLongitude()+":;";
		
		System.out.println("[Server] - Sending "+messageToSend);
		
		setPackageMessage(messageToSend);
		
		try {
			socket.send(packet);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public void kill(){
		running = false;
	}

}